/*
 * Copyright (C) 2015-present, Ant Financial Services Group
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
//
// Created by Cathor on 17/8/29.
//

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#include <string.h>
#include <stdlib.h>
#include <android/log.h>
#include <stdio.h>


#define TAG "CPU_JNI" // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGI类型

typedef struct PidData{

    struct PidData* next;
    int pid;
    long long previousAppUsage;
} PidData;

static PidData root = {
        .next = NULL,
        .pid = -1,
        .previousAppUsage = 0
};

static long long previousTotalUsage = 0;
static long long previousIdle = 0;

/*
 * 获取总CPU占用率
 * Class:     com_alipay_hulu_shared_display_items_CPUTools
 * Method:    getTotalUsage
 * Signature: ()F
 */
jfloat Java_com_alipay_hulu_shared_display_items_CPUTools_getTotalUsage(JNIEnv* env, jclass clz) {
    FILE * pFile;
    char* filename = "/proc/stat";


    pFile = fopen(filename, "r");
    if (pFile == NULL) {
        return 0;
    }
    char* line = (char*) malloc(1024);
    char* tmp;
    fgets(line, 1024, pFile);
    char* pch = strtok_r(line," \n", &tmp);
    int count = 0;
    long long currentTotalUsage = 0;
    long long currentIdle = 0;
    while (pch != NULL) {
        if (count > 7) {
            break;
        }
        if (count == 4) {
            currentIdle = atoll(pch);
        }
        if (count > 0) {
            currentTotalUsage += atoll(pch);
        }
        pch = strtok_r(NULL, " \n", &tmp);
        count++;
    }

    free(line);
    fclose(pFile);

    if (previousTotalUsage == 0L) {
        previousTotalUsage = currentTotalUsage;
        previousIdle = currentIdle;
        return 0;
    }

    // 计算时间段内cpu运行时间与idle时间
    long cpuTotalTime = currentTotalUsage - previousTotalUsage;
    long cpuIdleTime = currentIdle - previousIdle;

    // 转化为double计算结果
    double result = 100 * (cpuTotalTime - cpuIdleTime) / (double) cpuTotalTime;

    // 同步到上一次结果
    previousTotalUsage = currentTotalUsage;
    previousIdle = currentIdle;

    return (float)result;
}


static int count = 0;

/*
 * 获取应用CPU占用率与CPU总占用率
 * Class:     com_alipay_hulu_shared_display_items_CPUTools
 * Method:    getAppTotalUsage
 * Signature: (I)[F
 */
jfloatArray
Java_com_alipay_hulu_shared_display_items_CPUTools_getAppTotalUsage(JNIEnv *env, jclass clz, jintArray pids) {

    jboolean b;
    jint *realPids = (*env)->GetIntArrayElements(env, pids, &b);

    int length = (*env)->GetArrayLength(env, pids);

    FILE *cpuFile;
    FILE **appFiles = (FILE**) malloc(sizeof(FILE*) * length);

    float *data = (float *) malloc(sizeof(float) * (1 + length));

    // 尽量保证同时打开文件，同时获取文件数据
    cpuFile = fopen("/proc/stat", "r");
    if (cpuFile == NULL) {
        free(appFiles);
        LOGI("Open CPU file failed");
        jfloatArray result = (*env)->NewFloatArray(env, 0);
        free(data);
        (*env)->ReleaseIntArrayElements(env, pids, realPids, 0);
        return result;
    }

    // 逐项格式化文件名
    for (int i = 0; i < length; ++i) {
        char filename[20];
        sprintf(filename, "/proc/%d/stat", realPids[i]);
        appFiles[i] = fopen(filename, "r");
    }

//    // 存在文件不能打开的情况，Android 7.0以上无法直接打开/proc/pid/stat
//    if (appFile == NULL) {
//        fclose(cpuFile);
//        jfloat cpuUsage = Java_com_alipay_hulu_display_CPUTools_getTotalUsage(env, clz);
//        LOGI("Open app file failed");
//        jfloatArray result = (*env)->NewFloatArray(env, cpuUsage);
//        free(data);
//        return result;
//    }

    // 获取第一行数据
    char *cpuLine = (char *) malloc(256);
    fgets(cpuLine, 256, cpuFile);
    char **appLines = (char **) malloc(sizeof(char *) * length);
    for (int i = 0; i < length; ++i) {
        if (appFiles[i] != NULL) {
            char *appLine = (char *) malloc(256);
            fgets(appLine, 256, appFiles[i]);
            appLines[i] = appLine;
        } else {
            appLines[i] = NULL;
        }
    }

    // 文件读取完毕，关闭文件
    fclose(cpuFile);
    for (int i = 0; i < length; ++i) {
        if (appFiles[i] != NULL) {
            fclose(appFiles[i]);
        }
    }
    free(appFiles);

    // 解析CPU数据
    char* tmp;
    char *pch = strtok_r(cpuLine, " \n", &tmp);
    int count = 0;
    long long currentTotalUsage = 0;
    long long currentIdle = 0;

    // 第2-8位为CPU各项运行时间，第5位为CPU空闲时间
    while (pch != NULL) {
        if (!pch || *pch == '\0') {
            continue;
        }
        if (count > 7) {
            break;
        }
        if (count == 4) {
            currentIdle = atoll(pch);
        }
        if (count > 0) {
            currentTotalUsage += atoll(pch);
        }
        pch = strtok_r(NULL, " \n", &tmp);
        count++;
    }

    // 清理CPU行内存
    free(cpuLine);

    if (previousTotalUsage == 0L) {
        previousTotalUsage = currentTotalUsage;
        previousIdle = currentIdle;
        jfloatArray result = (*env)->NewFloatArray(env, 0);
        free(data);
        (*env)->ReleaseIntArrayElements(env, pids, realPids, 0);
        return result;
    }

    // 计算时间段内cpu运行时间与idle时间
    long cpuTotalTime = currentTotalUsage - previousTotalUsage;
    long cpuIdleTime = currentIdle - previousIdle;
    double cpuUsage = 100 * (cpuTotalTime - cpuIdleTime) / (double) cpuTotalTime;

    // 替换前一次数据
    previousTotalUsage = currentTotalUsage;
    previousIdle = currentIdle;

    // 存入结果
    data[length] = (float) cpuUsage;

    for (int i = 0; i < length; ++i) {

        // 对于空数据，直接跳过
        if (appLines[i] == NULL) {
            data[i] = 0;
            continue;
        }

        char* save_tmp;
        pch = strtok_r(appLines[i], " \n", &save_tmp);
        count = 0;
        long long currentAppUsage = 0;

        // 计算应用占用总时间，从13到16位分别为用户态运行，核心态运行时间，已死线程用户态运行时间，已死线程核心态运行时间
        while (pch != NULL) {
            // 空字符，忽略
            if (!pch || *pch == '\0') {
                continue;
            }
            if (count > 16) {
                break;
            }
            if (count > 12) {
                currentAppUsage += atoll(pch);
            }
            pch = strtok_r(NULL, " \n", &save_tmp);
            count++;
        }

        // 清理应用行内存
        free(appLines[i]);

        PidData *currentPidData = &root;
        while (currentPidData->pid != realPids[i] && currentPidData->next != NULL) {
            currentPidData = currentPidData->next;
        }

        LOGD("Pid: %d with data: %lld", realPids[i], currentAppUsage);

        // 当pid不为目标pid，说明未找到，在末尾添加新的PidData
        if (currentPidData->pid != realPids[i]) {
            PidData *newItem = (PidData *) malloc(sizeof(PidData));
            newItem->next = NULL;
            newItem->previousAppUsage = 0;
            newItem->pid = realPids[i];

            currentPidData->next = newItem;
            currentPidData = newItem;
        }

        // 未初始化，清理
        if (currentPidData->previousAppUsage == 0) {
            currentPidData->previousAppUsage = currentAppUsage;
            data[i] = 0;
            continue;
        }

        long appTime = currentAppUsage - currentPidData->previousAppUsage;
        double appUsage = 100 * appTime / (double) cpuTotalTime;

        data[i] = (float) appUsage;
        // 替换前一次数据
        currentPidData->previousAppUsage = currentAppUsage;
    }

    // 拷贝结果数据
    jfloatArray result = (*env)->NewFloatArray(env, length + 1);
    (*env)->SetFloatArrayRegion(env, result, 0, length + 1, data);
    free(data);
    (*env)->ReleaseIntArrayElements(env, pids, realPids, 0);
    return result;
}